package com.example.shutdown_v1.service;

import com.example.shutdown_v1.dto.RecognitionResult;
import com.example.shutdown_v1.dto.SensorDataPacket;
import com.example.shutdown_v1.entity.Sensor;
import com.example.shutdown_v1.entity.SensorBaselineHistory;
import com.example.shutdown_v1.repository.SensorBaselineHistoryRepository;
import com.example.shutdown_v1.repository.SensorRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
 * Valve State Recognition Service
 * 
 * Supports IM600 sensor recognition algorithms:
 * - Multi-turn detection: angle diff < 10°, turn count diff < 0.4
 * - Lever valve: axis selection based on install direction
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ValveStateRecognitionService {

    private final SensorRepository sensorRepository;
    private final SensorBaselineHistoryRepository historyRepository;

    private static final double IM600_HORIZONTAL_THRESHOLD = 0.3;
    private static final double IM600_VERTICAL_THRESHOLD = 5.0;
    private static final double CLOSE_THRESHOLD = 5.0;

    /**
     * Main entry point for valve state recognition
     */
    public RecognitionResult recognizeState(SensorDataPacket packet, Sensor sensor) {
        String sensorId = packet.getMac();
        
        if (sensor == null) {
            log.warn("Sensor entity is null, sensorId={}", sensorId);
            return RecognitionResult.builder()
                    .state("unknown")
                    .minDistance(null)
                    .confidence(0.0)
                    .build();
        }
        
        Integer useMultiTurn = sensor.getUseMultiTurnDetection();
        
        if (useMultiTurn != null && useMultiTurn == 1) {
            log.debug("Using multi-turn detection, sensorId={}", sensorId);
            return recognizeByMultiTurnIM600(packet, sensor);
        } else {
            log.debug("Using lever valve detection, sensorId={}", sensorId);
            return recognizeByEulerIM600(packet, sensor);
        }
    }
    
    /**
     * IM600 auto-calibration with weighted average (0.7 * old + 0.3 * new)
     */
    private void performIM600AutoCalibration(Sensor sensor, String sensorId, SensorDataPacket packet) {
        Double currentRoll = packet.getRoll();
        Double currentPitch = packet.getPitch();
        Double currentYaw = packet.getYaw();
        Double accelZ = packet.getAccelZ();
        
        Integer installDirection = sensor.getInstallDirection();
        if (installDirection == null) {
            installDirection = 0; // 默认垂直
        }
        
        // 计算基准值年龄
        int baselineAgeDays = 0;
        if (sensor.getBaselineUpdateTime() != null) {
            baselineAgeDays = (int) ChronoUnit.DAYS.between(
                    sensor.getBaselineUpdateTime(), LocalDateTime.now());
        }
        
        // 获取旧的基准值
        Double oldRoll = sensor.getRollBaselineClosed();
        Double oldPitch = sensor.getPitchBaselineClosed();
        Double oldYaw = sensor.getYawBaselineClosed();
        Double oldAccelZ = sensor.getAccelBaselineZ();
        
        // 加权平均计算新基准值（70%旧值 + 30%新值）
        // 这样可以逐步适应机械偏差，避免突变
        final double OLD_WEIGHT = 0.7;  // 旧值权重
        final double NEW_WEIGHT = 0.3;  // 新值权重
        
        Double newRoll = (oldRoll != null) ? oldRoll * OLD_WEIGHT + currentRoll * NEW_WEIGHT : currentRoll;
        Double newPitch = (oldPitch != null) ? oldPitch * OLD_WEIGHT + currentPitch * NEW_WEIGHT : currentPitch;
        Double newYaw = (oldYaw != null) ? oldYaw * OLD_WEIGHT + currentYaw * NEW_WEIGHT : currentYaw;
        Double newAccelZ = (oldAccelZ != null) ? oldAccelZ * OLD_WEIGHT + accelZ * NEW_WEIGHT : accelZ;
        
        // 计算偏差（用于日志）
        double rollDiff = (oldRoll != null) ? Math.abs(currentRoll - oldRoll) : 0.0;
        double pitchDiff = (oldPitch != null) ? Math.abs(currentPitch - oldPitch) : 0.0;
        double yawDiff = (oldYaw != null) ? Math.abs(currentYaw - oldYaw) : 0.0;
        
        // 记录历史
        SensorBaselineHistory history = SensorBaselineHistory.builder()
                .sensorId(sensorId)
                .sensorModel(sensor.getSensorModel())
                .accelBaselineX(null)
                .accelBaselineY(null)
                .accelBaselineZ(newAccelZ)
                .rollBaselineClosed(newRoll)
                .pitchBaselineClosed(newPitch)
                // 关键：水平安装(1)不记录Yaw
                .yawBaselineClosed(installDirection == 1 ? null : newYaw)
                .createdAt(LocalDateTime.now())
                .calibrationType("auto_weighted")  // 标记为加权更新
                .baselineAgeDays(baselineAgeDays)
                .build();
        historyRepository.save(history);

        // 更新传感器基准值（使用加权平均后的值）
        sensor.setRollBaselineClosed(newRoll);
        sensor.setPitchBaselineClosed(newPitch);
        sensor.setAccelBaselineZ(newAccelZ);
        
        // 关键：只有非水平安装(即0垂直)才更新Yaw
        if (installDirection != 1) {
            sensor.setYawBaselineClosed(newYaw);
        }
        
        sensor.setBaselineUpdateTime(LocalDateTime.now());
        sensorRepository.save(sensor);

        // 输出日志（包含偏差信息）
        if (installDirection == 1) {
            log.info("[{}] [IM600-水平] 自动校准完成 (age={}天) - 偏差: Roll={:.2f}° Pitch={:.2f}° - 新基准: R={:.2f}° P={:.2f}° AccelZ={:.4f}",
                    sensorId, baselineAgeDays, rollDiff, pitchDiff, newRoll, newPitch, newAccelZ);
        } else {
            log.info("[{}] [IM600-垂直] 自动校准完成 (age={}天) - 偏差: Roll={:.2f}° Pitch={:.2f}° Yaw={:.2f}° - 新基准: R={:.2f}° P={:.2f}° Y={:.2f}° AccelZ={:.4f}",
                    sensorId, baselineAgeDays, rollDiff, pitchDiff, yawDiff, newRoll, newPitch, newYaw, newAccelZ);
        }
    }
    
    /**
     * 识别阀门状态（IM600专用 - 根据安装方向选择轴数）
     * 
     * 原理:
     * - 水平安装: 只计算 X轴(Roll) + Y轴(Pitch)，忽略Z轴(Yaw)防止不定时偏移影响
     * - 垂直/斜安装: 计算 X轴(Roll) + Y轴(Pitch) + Z轴(Yaw) 三轴综合
     * - 阈值: 5度
     * 
     * @param packet 传感器数据包
     * @param sensor 传感器实体(包含基准值)
     * @return 识别结果
     */
    public RecognitionResult recognizeByEulerIM600(SensorDataPacket packet, Sensor sensor) {
        String sensorId = packet.getMac();
        
        // 1. 验证欧拉角数据（需要三轴数据）
        if (packet.getRoll() == null || packet.getPitch() == null || packet.getYaw() == null) {
            log.warn("Incomplete Euler angle data, sensorId={}", sensorId);
            return RecognitionResult.builder()
                    .state("unknown")
                    .minDistance(null)
                    .confidence(0.0)
                    .build();
        }
        
        // 2. 检查基准值（需要三轴基准值）
        if (sensor == null || 
            sensor.getRollBaselineClosed() == null ||
            sensor.getPitchBaselineClosed() == null || 
            sensor.getYawBaselineClosed() == null) {
            log.info("First data received, setting baseline as closed state, sensorId={}, roll={}, pitch={}, yaw={}", 
                    sensorId, packet.getRoll(), packet.getPitch(), packet.getYaw());
            return RecognitionResult.builder()
                    .state("closed")
                    .minDistance(0.0)
                    .confidence(1.0)
                    .build();
        }
        
        // 3. 计算三轴与基准值的差异
        double currentRoll = packet.getRoll();
        double currentPitch = packet.getPitch();
        double currentYaw = packet.getYaw();
        
        double baselineRoll = sensor.getRollBaselineClosed();
        double baselinePitch = sensor.getPitchBaselineClosed();
        double baselineYaw = sensor.getYawBaselineClosed();
        
        double diffRoll = Math.abs(currentRoll - baselineRoll);
        double diffPitch = Math.abs(currentPitch - baselinePitch);
        double diffYaw = Math.abs(currentYaw - baselineYaw);
        
        if (diffRoll > 180) diffRoll = 360 - diffRoll;
        if (diffPitch > 180) diffPitch = 360 - diffPitch;
        if (diffYaw > 180) diffYaw = 360 - diffYaw;
        
        Integer installDirection = sensor.getInstallDirection();
        if (installDirection == null) installDirection = 0;
        
        double totalDiff;
        String axesUsed;
        String directionName = (installDirection == 1) ? "horizontal" : "vertical";
        
        if (installDirection == 1) {
            totalDiff = Math.sqrt(Math.pow(diffRoll, 2) + Math.pow(diffPitch, 2));
            axesUsed = "XY";
        } else {
            totalDiff = Math.sqrt(Math.pow(diffRoll, 2) + Math.pow(diffPitch, 2) + Math.pow(diffYaw, 2));
            axesUsed = "XYZ";
        }
        
        log.debug("Angles - current: R={}, P={}, Y={}, baseline: R={}, P={}, Y={}, diff: R={}, P={}, Y={}, sensorId={}, dir={}", 
                currentRoll, currentPitch, currentYaw, baselineRoll, baselinePitch, baselineYaw, 
                diffRoll, diffPitch, diffYaw, sensorId, directionName);
        
        double threshold = (installDirection == 1) ? IM600_HORIZONTAL_THRESHOLD : IM600_VERTICAL_THRESHOLD;
        boolean isClosed = totalDiff < threshold;
        String state = isClosed ? "closed" : "open";
        double confidence = isClosed ? 
                Math.max(0.75, 1.0 - (totalDiff / threshold) * 0.25) : 
                Math.min(0.95, 0.75 + (totalDiff / threshold) * 0.15);
        
        log.debug("Recognition result: sensorId={}, dir={}, axes={}, totalDiff={}, threshold={}, state={}, confidence={}", 
                sensorId, directionName, axesUsed, totalDiff, threshold, state, confidence);
        
        // 注意：自动校准不在这里执行，而是由定时任务统一执行（每天早上8点）
        
        return RecognitionResult.builder()
                .state(state)
                .minDistance(totalDiff)
                .confidence(confidence)
                .build();
    }
    
    /**
     * 公开方法：供定时任务调用，执行自动校准
     * @param sensor 传感器实体
     * @param packet 当前传感器数据包
     */
    public void triggerIM600AutoCalibration(Sensor sensor, SensorDataPacket packet) {
        performIM600AutoCalibration(sensor, sensor.getSensorId(), packet);
    }
    
    /**
     * IM600多圈检测状态识别（角度+圈数双重判断）
     * 
     * 原理:
     * - 圈数阈值: 0.4
     * - 角度阈值: 0.3度
     * - 判断逻辑: 圈数>0.4 且 角度>0.3 就算开
     * 
     * @param packet 传感器数据包
     * @param sensor 传感器实体(包含基准值)
     * @return 识别结果
     */
    public RecognitionResult recognizeByMultiTurnIM600(SensorDataPacket packet, Sensor sensor) {
        String sensorId = packet.getMac();
        
        // 1. 验证数据完整性（需要角度和圈数数据）
        if (packet.getRoll() == null || packet.getPitch() == null || packet.getYaw() == null ||
            packet.getRollTurnCount() == null || packet.getYawTurnCount() == null) {
            log.warn("Incomplete multi-turn data, sensorId={}", sensorId);
            return RecognitionResult.builder()
                    .state("unknown")
                    .minDistance(null)
                    .confidence(0.0)
                    .build();
        }
        
        // 2. 检查基准值（需要角度和圈数基准值）
        if (sensor == null || 
            sensor.getRollBaselineClosed() == null ||
            sensor.getPitchBaselineClosed() == null || 
            sensor.getYawBaselineClosed() == null ||
            sensor.getRollTurnBaselineClosed() == null ||
            sensor.getYawTurnBaselineClosed() == null) {
            log.info("First multi-turn data, setting baseline, sensorId={}, roll={}({} turns), pitch={}, yaw={}({} turns)", 
                    sensorId, packet.getRoll(), packet.getRollTurnCount(), 
                    packet.getPitch(), 
                    packet.getYaw(), packet.getYawTurnCount());
            return RecognitionResult.builder()
                    .state("closed")
                    .minDistance(0.0)
                    .confidence(1.0)
                    .build();
        }
        
        // Calculate angle differences
        double currentRoll = packet.getRoll();
        double currentPitch = packet.getPitch();
        double currentYaw = packet.getYaw();

        double baselineRoll = sensor.getRollBaselineClosed();
        double baselinePitch = sensor.getPitchBaselineClosed();
        double baselineYaw = sensor.getYawBaselineClosed();

        double diffRoll = Math.abs(currentRoll - baselineRoll);
        double diffPitch = Math.abs(currentPitch - baselinePitch);
        double diffYaw = Math.abs(currentYaw - baselineYaw);

        if (diffRoll > 180) diffRoll = 360 - diffRoll;
        if (diffPitch > 180) diffPitch = 360 - diffPitch;
        if (diffYaw > 180) diffYaw = 360 - diffYaw;

        // Calculate turn count differences
        double currentRollTurn = packet.getRollTurnCount();
        double currentYawTurn = packet.getYawTurnCount();

        double baselineRollTurn = sensor.getRollTurnBaselineClosed();
        double baselineYawTurn = sensor.getYawTurnBaselineClosed();

        double diffRollTurn = Math.abs(currentRollTurn - baselineRollTurn);
        double diffYawTurn = Math.abs(currentYawTurn - baselineYawTurn);

        Integer installDirection = sensor.getInstallDirection();
        if (installDirection == null) installDirection = 0;
        
        String directionName = (installDirection == 1) ? "horizontal" : "vertical";
        
        final double TURN_THRESHOLD = 0.4;
        final double ANGLE_THRESHOLD = 10.0;

        // Vertical multi-turn: use X-axis (Roll) only
        double totalAngleDiff = diffRollTurn * 360.0;
        double turnDiffForDecision = diffRollTurn;
        double angleDiffForDecision = diffRoll;
        
        String axesUsed = "X(Roll)";
        boolean isAngleLarge = angleDiffForDecision > ANGLE_THRESHOLD;
        boolean isTurnLarge = turnDiffForDecision > TURN_THRESHOLD;
        boolean isOpen = isAngleLarge || isTurnLarge;  // 任意一个超标就算开

        String state = isOpen ? "open" : "closed";
        double ratio = Math.min(turnDiffForDecision / TURN_THRESHOLD, 2.0);
        double confidence = isOpen
                ? Math.min(0.95, 0.75 + 0.2 * ratio)
                : Math.max(0.75, 1.0 - 0.25 * ratio);

        log.debug("Multi-turn result: sensorId={}, dir={}, axes={}, totalAngle={}, angleDiff={}, turnDiff={}, state={}, confidence={}",
                sensorId, directionName, axesUsed, totalAngleDiff, angleDiffForDecision, turnDiffForDecision, state, confidence);

        log.debug("Decision: sensorId={}, angle {}>{} OR turns {}>{} => {}",
                sensorId, angleDiffForDecision, ANGLE_THRESHOLD, turnDiffForDecision, TURN_THRESHOLD, state);

        return RecognitionResult.builder()
                .state(state)
                .minDistance(totalAngleDiff)
                .confidence(confidence)
                .build();
    }
    
}
